<?php

/* ========================================================================= */
/* This file is part of the HypnoPHP Framework Library                       */
/* Copyright (c) 2009-2010, Radu Buzea                                       */
/* ========================================================================= */

/**
* @author Radu Buzea
* @copyright Copyright (c) 2009-2010, Radu Buzea (www.hypnosis.ro)
* @license GNU Lesser General Public License (www.gnu.org/copyleft/lesser.html)
* @package HypnoPHP
* @category Core
* @version 1.0.3
*/

defined('HYPNOPHP_APPLICATION') or
die("Cannot use <i>" . basename(__FILE__) . "</i> outside HypnoPHP Application");

// ============================================================================
// Final class: HRequest
// ============================================================================

final class HRequest extends HBaseObject
{
	/**
	* Initialize the request params (static method)
	*
	* @return void
	*/
	public static function initRequest()
	{
		$request = self::getInstance();
		$request->request_uri = isset($_SERVER['REQUEST_URI']) ? $_SERVER['REQUEST_URI'] : '';
		$request->server = new HCollection(); // list of $_SERVER params
		$request->params = new HCollection(); // list of all request params (depends on the request method)
		$request->uri_parts = self::getURIParts();
		$request->is_mobile_device = false;

		$request->server->assign($_SERVER);
		// Set all params - get/post/put, raw_post_data, and files...
		switch (self::method()) {
			case 'get':
				$params = $_GET;
				break;
			case 'post':
				$params = $_POST;
				if (self::isFacebook()) { $params = array_merge($params, $_GET); }
				break;
			case 'put':
				$params = self::getPutParams();
				break;
			default:
				$params = array();
		}
		$params = array_merge_recursive($params, self::normalizeFiles());
		$params['http_raw_post_data'] = isset($GLOBALS['HTTP_RAW_POST_DATA']) ? $GLOBALS['HTTP_RAW_POST_DATA'] : '';
		$request->params->assign($params);
	}

	# ###########################################################################

	/**
	* Get the parts from URI string (static method)
	*
	* @param string $uri_str
	* @return array
	*/
	public static function getURIParts($uri_str = '')
	{
		$request = self::getInstance();
		$result = array();
		// Get de URI string
		if ($uri_str == '') {
			if (HApplication::rewriteMode()) {
				// ...the URI string if the server support rewrite rules
				$uri_str = $request->request_uri;
			} else if (isset($_GET['nav'])) {
				// ...the URI string given by the 'nav' GET paramater
				$uri_str = trim($_GET['nav']);
			} else if (count($_GET) > 0){
				// ...the URI string given by the first GET paramater
				foreach ($_GET as $key => $value) {
					if (strstr($key, '/') && $value == '') {
						$uri_str = $key;
						break;
					}
				}
			}
			if ($uri_str == '' && isset($_SERVER['REQUEST_URI'])) {
				// ...the URI string from the SERVER REQUEST_URI parameter
				// There must have been something wrong if the app goes here
				// This occurs when the server doesn't support rewrite rules
				$uri_str = $_SERVER['REQUEST_URI'];
			}
		}

		// Check if the URI string is empty (and return empty result)
		if (trim($uri_str) == '') { return $result; }
		$uri_str = trim($uri_str, '/');

		// Clean up the URI string for any GET parameters
		if (strstr($uri_str, '?')) {
			$p = strpos($uri_str, '?');
			$uri_str = substr($uri_str, 0, $p);
		}
		if (strstr($uri_str, '&')) {
			$p = strpos($uri_str, '&');
			$uri_str = substr($uri_str, 0, $p);
		}
		// Break the URI string in parts
		if (strstr($uri_str, '/')) {
			$result = explode('/', $uri_str);
		} else {
			$result = array($uri_str);
		}
		return $result;
	}

	# ###########################################################################

	/**
	* Get PUT params (internal static method)
	*
	* @return array
	*/
	protected static function getPutParams()
	{
		$result = array();
		$raw_put_data = trim(file_get_contents("php://input"));
		if ($raw_put_data != '') {
			$raw_put_params = explode("&", $raw_put_data);
			if (is_array($raw_put_params)) {
				foreach ($raw_put_params as $param) {
					$param_parts = explode("=", $param);
					if (is_array($param_parst) && count($param_parst) == 2) {
						$param_name = urldecode($param_parts[0]);
						$param_value = urldecode($param_parts[1]);
						$result[$param_name] = $param_value;
					}
				}
			}
		}
		return $result;
	}

	# ###########################################################################

	/**
	* Normalize FILES params (internal static method)
	*
	* @param array $params
	* @param bool $first_call
	* @return array
	*/
	protected static function normalizeFiles($params = null, $first_call = true)
	{
		$params = $first_call ? $_FILES : $params;
		$result = array();
		$params = array_diff($params,array(''));
		if (!empty($params) && is_array($params)) {
			foreach ($params as $name => $details) {
				if(is_array($details) && !empty($details['name']) &&
					!empty($details['tmp_name']) && !empty($details['size'])) {
					if (is_array($details['tmp_name'])) {
						foreach ($details['tmp_name'] as $item => $item_details) {
							if (is_array($item_details)) {
								foreach (array_keys($item_details) as $key) {
									if (UPLOAD_ERR_NO_FILE != $details['error'][$item][$key]) {
										$result[$name][$item][$key] = array(
											'name' => $details['name'][$item][$key],
											'tmp_name' => $details['tmp_name'][$item][$key],
											'size' => $details['size'][$item][$key],
											'type' => $details['type'][$item][$key],
											'error' => $details['error'][$item][$key],
										);
									}
								}
							} else {
								if ($details['error'][$item] != UPLOAD_ERR_NO_FILE) {
									$result[$name][$item] = array(
										'name' => $details['name'][$item],
										'tmp_name' => $details['tmp_name'][$item],
										'size' => $details['size'][$item],
										'type' => $details['type'][$item],
										'error' => $details['error'][$item],
									);
								}
							}
						}
					} else if ($first_call) {
						$result[$name] = $details;
					} else {
						$result[$name][] = $details;
					}
				} else if(is_array($details)) {
					$nested = self::normalizeFiles($details, false);
					if (!empty($nested)) {
						$result = array_merge(array($name=>$nested), $result);
					}
				}
			}
		}
		return $result;
	}

	# ###########################################################################

	/**
	* Get SERVER params (static method)
	*
	* @return array
	*/
	public static function server($var_name = '')
	{
		$request = self::getInstance();
		if ($var_name != '') {
			$var_name = trim(strtolower($var_name));
			return $request->server->$var_name;
		}
		return $request->server;
	}

	# ###########################################################################

	/**
	* Get all REQUEST params (static method)
	*
	* @return array
	*/
	public static function params($var_name = '')
	{
		$request = self::getInstance();
		if ($var_name != '') {
			$var_name = trim(strtolower($var_name));
			return $request->params->$var_name;
		}
		return $request->params;
	}

	# ###########################################################################

	/**
	* Get the request URI (static method)
	*
	* @return string
	*/
	public static function requestURI()
	{
		$request = self::getInstance();
		return $request->request_uri;
	}

	# ###########################################################################

	/**
	* Internal re-compiled request URI (static method)
	*
	* @return string
	*/
	public static function internalRequestURI()
	{
		$request = self::getInstance();
		$uri_parts = $request->uri_parts;
		$result = '';
		if (is_array($uri_parts) && count($uri_parts) > 0) {
			foreach ($uri_parts as $part) { $result .= '/' . $part; }
		}
		if ($result == '') { return $request->request_uri; }
		return $result;
	}

	# ###########################################################################

	/**
	* Get the request URL (static method)
	*
	* @return string
	*/
	public static function url()
	{
		return self::uri() . self::requestURI();
	}

	# ###########################################################################

	/**
	* Check if is an AJAX call (static method)
	*
	* @return bool
	*/
	public static function isAjax()
	{
		$result = self::server('http_x_requested_with');
		return (!is_null($result) && strstr(strtoupper($result), 'XMLHTTPREQUEST') !== false);
	}

	# ###########################################################################

	/**
	* Get the request method (static method)
	*
	* @return string ('get', 'put', 'post')
	*/
	public static function method()
	{
		$server = self::server();
		return isset($server->request_method) ? strtolower($server->request_method) : 'get';
	}

	# ###########################################################################

	/**
	* Check if is SSL call (static method)
	*
	* @return bool
	*/
	public static function isSSL()
	{
		$server = self::server();
		return (isset($server->https) && boolval($server->https));
	}

	# ###########################################################################

	/**
	* Get the request protocol: 'http' or 'https' (static method)
	*
	* @return string
	*/
	public static function protocol()
	{
		$server = self::server();
		return (!empty($server->https) && boolval($server->https)) ? 'https://' : 'http://';
	}

	# ###########################################################################

	/**
	* Get the standard request port of the requested protocol (static method)
	*
	* @return integer
	*/
	public static function standardPort()
	{
		$server = self::server();
		return (!empty($server->https) && boolval($server->https)) ? 443 : 80;
	}

	# ###########################################################################

	/**
	* Get the request port (static method)
	*
	* @return integer
	*/
	public static function port()
	{
		$server = self::server();
		return !empty($server->server_port) ? intval($server->server_port) : 80;
	}

	# ###########################################################################

	/**
	* Get the request port string (static method)
	*
	* @return string
	*/
	public static function portStr()
	{
		$port = self::port();
		$standard_port = self::standardPort();
		return $port == $standard_port ? '' : ($port ? ':' . $port : '');
	}

	# ###########################################################################

	/**
	* Get the host string (static method)
	*
	* @return string
	*/
	public static function host()
	{
		$server = self::server();
		return (!empty($server->server_name) && $server->server_name != '') ? $server->server_name : 'localhost';
	}

	# ###########################################################################

	/**
	* Check if the server is a 'localhost' (static method)
	*
	* @return bool
	*/
	public static function isLocalhost()
	{
		return (self::host() == 'localhost');
	}

	# ###########################################################################

	/**
	* Get a string with host and port (if any) (static method)
	*
	* @return string
	*/
	public static function hostPort()
	{
		return self::host() . self::portStr();
	}

	# ###########################################################################

	/**
	* Get a string with the subdomain (if any) (static method)
	*
	* @return string
	*/
	public static function subdomain()
	{
		$host_parts = explode('.', self::host());
		$result = '';
		switch (count($host_parts)) {
			case 1:
			case 2:
				break;
			case 3:
				if (strtolower($host_parts[0]) != 'www') {
					$result = $host_parts[0];
				}
				break;
			default:
				for ($i = 0; $i < count($host_parts) - 2; $i++) {
					if ($result != '') { $result.= '.'; }
					$result.= $host_parts[$i];
				}
		}
		return $result;
	}

	# ###########################################################################

	/**
	* Get a URI string with protocol, host and port (static method)
	*
	* @return string
	*/
	public static function uri()
	{
		return self::protocol() . self::hostPort();
	}

	# ###########################################################################

	/**
	* Check if is GET method (static method)
	*
	* @return bool
	*/
	public static function isGet()
	{
		return (self::method() == 'get');
	}

	# ###########################################################################

	/**
	* Check if is POST method (static method)
	*
	* @return bool
	*/
	public static function isPost()
	{
		return (self::method() == 'post');
	}

	# ###########################################################################

	/**
	* Check if is PUT method (static method)
	*
	* @return bool
	*/
	public static function isPut()
	{
		return (self::method() == 'put');
	}

	# ###########################################################################

	/**
	* Check if is DELETE method (static method)
	*
	* @return bool
	*/
	public static function isDelete()
	{
		return (self::method() == 'delete');
	}

	# ###########################################################################

	/**
	* Check if is HEAD method (static method)
	*
	* @return bool
	*/
	public static function isHead()
	{
		return (self::method() == 'head');
	}

	# ###########################################################################

	/**
	* Check if is a Facebook.com call (static method)
	* @return bool
	*/
	public static function isFacebook()
	{
		$server = self::server();
		return (isset($server->http_x_fb_user_remote_addr) &&
			$server->http_x_fb_user_remote_addr != '');
	}

	# ###########################################################################

	/**
	* Check if the client is using a mobile (phone, PDA) browser (static method)
	*
	* @return bool
	*/
	public static function isMobileBrowser()
	{
		$mobile_device = array(
			'iphone' => false,
			'android' => false,
			'opera' => false,
			'blackberry' => false,
			'palm' => false,
			'windows' => false,
			'mobile' => false,
			'wap' => false,
		);
		$mobile_browser = false;
		$server = self::server();
		$agent = empty($server->http_user_agent) ? '' : trim($server->http_user_agent);
		$accept = empty($server->http_accept) ? '' : trim($server->http_accept);
		if (eregi('ipod', $agent) || eregi('iphone', $agent)) {
			$mobile_browser = true;
			$mobile_device['iphone'] = true;
		}
		if (preg_match('/(mini 9.5|vx1000|lge |m800|e860|u940|ux840|compal|wireless| mobi|ahong|lg380|lgku|lgu900|lg210|lg47|lg920|lg840|lg370|sam-r|mg50|s55|g83|t66|vx400|mk99|d615|d763|el370|sl900|mp500|samu3|samu4|vx10|xda_|samu5|samu6|samu7|samu9|a615|b832|m881|s920|n210|s700|c-810|_h797|mob-x|sk16d|848b|mowser|s580|r800|471x|v120|rim8|c500foma:|160x|x160|480x|x640|t503|w839|i250|sprint|w398samr810|m5252|c7100|mt126|x225|s5330|s820|htil-g1|fly v71|s302|-x113|novarra|k610i|-three|8325rc|8352rc|sanyo|vx54|c888|nx250|n120|mtk |c5588|s710|t880|c5005|i;458x|p404i|s210|c5100|teleca|s940|c500|s590|foma|samsu|vx8|vx9|a1000|_mms|myx|a700|gu1100|bc831|e300|ems100|me701|me702m-three|sd588|s800|8325rc|ac831|mw200|brew |d88|htc\/|htc_touch|355x|m50|km100|d736|p-9521|telco|sl74|ktouch|m4u\/|me702|8325rc|kddi|phone|lg |sonyericsson|samsung|240x|x320vx10|nokia|sony cmd|motorola|up.browser|up.link|mmp|symbian|smartphone|midp|wap|vodafone|o2|pocket|kindle|mobile|psp|treo)/i', $agent)) {
			$mobile_browser = true;
			$mobile_device['other'] = true;
		}
		if ((strpos($accept, 'text/vnd.wap.wml') > 0) || (strpos($accept, 'application/vnd.wap.xhtml+xml') > 0)) {
			$mobile_browser = true;
			$mobile_device['wap'] = true;
		}
		if (!$mobile_device['wap'] && (isset($_SERVER['HTTP_X_WAP_PROFILE']) || isset($_SERVER['HTTP_PROFILE']))) {
			$mobile_browser = true;
			$mobile_device['wap'] = true;
		}
		if (eregi('android', $agent)) {
			$mobile_browser = true;
			$mobile_device['android'] = true;
		}
		if (eregi('opera mini', $agent) || ($mobile_browser && eregi('opera', $agent))) {
			$mobile_browser = true;
			$mobile_device['opera'] = true;
		}
		if (eregi('blackberry', $agent)) {
			$mobile_browser = true;
			$mobile_device['blackberry'] = true;
		}
		if (preg_match('/(pre\/|palm os|palm|hiptop|avantgo|fennec|plucker|xiino|blazer|elaine)/i', $agent)) {
			$mobile_browser = true;
			$mobile_device['palm'] = true;
		}
		if (preg_match('/(iris|3g_t|windows ce|opera mobi|windows ce; smartphone;|windows ce; iemobile|windows phone|dows 6.5)/i', $agent)) {
			$mobile_browser = true;
			$mobile_device['windows'] = true;
		}
		$request = self::getInstance();
		$request->is_mobile_device = $mobile_device;
		return $mobile_browser;
	}

	# ###########################################################################

	/**
	* Get the remote client's IP address (static method)
	*
	* @return string
	*/
	public static function remoteIP()
	{
		$server = self::server();
		if (!empty($server->http_client_ip)) {
			return $server->http_client_ip;
		}
		if (!empty($server->http_x_forwarded_for)) {
			$xfw = $server->http_x_forwarded_for;
			foreach ((strstr($xfw, ',') ? split(',', $xfw) : array($xfw)) as $remote_ip) {
				if ($remote_ip == 'unknown' ||
					preg_match('/^((25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])\.){3}(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])$/', $remote_ip) ||
					preg_match('/^([0-9a-fA-F]{4}|0)(\:([0-9a-fA-F]{4}|0)){7}$/', $remote_ip)
				) { return $remote_ip; }
			}
		}
		return empty($server->remote_addr) ? '' : $server->remote_addr;
	}

	# ###########################################################################

	/**
	* Internal sort_accept_list
	*/
	private function __sortAcceptList($item1, $item2)
	{
		return $item1['q'] == $item2['q'] ? ($item1['i'] > $item2['i']) : ($item1['q'] < $item2['q']);
	}

	# ###########################################################################

	/**
	* Get the HTTP accept list
	*
	* @return array
	*/
	public static function httpAcceptList()
	{
		$server = self::server();
		$result = array();
		$accept = empty($server->http_accept) ? '' : trim($server->http_accept);
		$accept_parts = explode(',', $accept);
		foreach ($accept_parts as $accept_key => $accept_value) {
			@list($type, $params) = explode(';', $accept_value);
			$mime_type_struct = array();
			if ($params) {
				parse_str($params, $mime_type_struct);
			}
			$mime_type_struct['type'] = trim($type);
			if (empty($mime_type_struct['q'])) {
				$mime_type_struct['q'] = '1.0';
			}
			$mime_type_struct['i'] = $accept_key;
			array_push($result, $mime_type_struct);
		}
		usort($result, array($request, 'sort_accept_list'));
		for ($i = 0; $i < count($result); $i++) {
			if (isset($result[$i]['i'])) {
				unset($result[$i]['i']);
			}
		}
		return $result;
	}

	# ###########################################################################

	/**
	* Accept language list property getter method
	*
	* @return array
	*/
	public function __getAcceptLanguageList()
	{
		$default_lang = 'en-us';
		$result = array();
		$server = self::server();
		$http_language = empty($server->http_accept_language) ? '' : trim($server->http_accept_language);
		$accept_parts = explode(',', $http_language);
		foreach ($accept_parts as $accept_key => $accept_value) {
			$parts = explode(';', $accept_value);
			if (isset($parts[0]) && trim($parts[0]) != '') {
				array_push($result, strtolower(trim($parts[0])));
			}
		}
		if (empty($result)) {
			$result = array('en-us');
		}
		return $result;
	}

	# ###########################################################################

	/**
	* Get the request object instance (singleton)
	*
	* @return HRequest
	*/
	public static function getInstance()
	{
		return self::singleton(__CLASS__);
	}

	# ###########################################################################

}
